package cn.iocoder.yudao.module.pay.order.service;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.NumberUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.extra.spring.SpringUtil;
import cn.hutool.json.JSONUtil;
import cn.iocoder.yudao.framework.business.basic.dao.query.CoreLambdaQueryWrapper;
import cn.iocoder.yudao.framework.business.basic.dao.repository.SerialNoRedisRepository;
import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
import cn.iocoder.yudao.framework.common.exception.Assertions;
import cn.iocoder.yudao.framework.common.exception.KnownServiceException;
import cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils;
import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.framework.common.util.spring.SpringUtils;
import cn.iocoder.yudao.framework.common.util.string.StrUtils;
import cn.iocoder.yudao.module.api.infrastructure.config.ConfigApiV2;
import cn.iocoder.yudao.module.api.infrastructure.config.dto.PayConfigRespDTO;
import cn.iocoder.yudao.module.api.infrastructure.social.SocialUserApi;
import cn.iocoder.yudao.module.api.infrastructure.social.dto.SocialUserRespDTO;
import cn.iocoder.yudao.module.api.infrastructure.social.enums.SocialTypeEnum;
import cn.iocoder.yudao.module.api.infrastructure.wechat.WechatClientApi;
import cn.iocoder.yudao.module.api.pay.order.dto.PayOrderCreateDTO;
import cn.iocoder.yudao.module.api.pay.order.dto.UnifiedOrderCreateDTO;
import cn.iocoder.yudao.module.api.pay.order.eums.UnifiedOrderStatusEnum;
import cn.iocoder.yudao.module.api.pay.order.eums.UnifiedPayChannelEnum;
import cn.iocoder.yudao.module.api.pay.order.vo.UnifiedOrderVO;
import cn.iocoder.yudao.module.pay.framework.PayClient;
import cn.iocoder.yudao.module.pay.framework.impl.cash.CashPayClient;
import cn.iocoder.yudao.module.pay.framework.impl.free.FreePayClient;
import cn.iocoder.yudao.module.pay.framework.impl.mallbook.MallBookWxLitePayClient;
import cn.iocoder.yudao.module.pay.framework.impl.mock.MockPayClient;
import cn.iocoder.yudao.module.pay.framework.impl.wallet.WalletPayClient;
import cn.iocoder.yudao.module.pay.order.convert.PayOrderConvert;
import cn.iocoder.yudao.module.pay.order.dal.dataobject.PayOrderDO;
import cn.iocoder.yudao.module.pay.order.dal.mysql.PayOrderMapper;
import cn.iocoder.yudao.module.pay.order.mq.PayOrderProducer;
import cn.iocoder.yudao.module.pay.order.vo.*;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import jakarta.annotation.Nonnull;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;

import java.time.LocalDateTime;
import java.util.*;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.framework.common.util.servlet.ServletUtils.getClientIP;
import static cn.iocoder.yudao.framework.common.util.servlet.ServletUtils.getUserAgent;
import static cn.iocoder.yudao.module.api.infrastructure.ConfigConstants.CONFIG_PAY;
import static cn.iocoder.yudao.module.api.pay.ConfigConstants.PAY_PAYMENT_NO_PREFIX;
import static cn.iocoder.yudao.module.api.pay.ConfigConstants.PAY_TIMEOUT_MINUTES;
import static cn.iocoder.yudao.module.api.pay.ErrorCodeConstants.*;

/**
 * 付款单据 Service 实现类
 *
 * @author aquan
 */
@Service
@Validated
@Slf4j
public class PayOrderServiceImpl implements PayOrderService {

    @Resource
    private PayOrderMapper payOrderMapper;

    @Resource
    private SerialNoRedisRepository orderSerialNoRedisRepository;
    @Resource
    private PayOrderProducer payOrderProducer;

    @Resource
    private ConfigApiV2 configApiV2;
    @Resource
    private WechatClientApi wechatClientApi;
    @Resource
    private SocialUserApi socialUserApi;

    private final Lock lock = new ReentrantLock();

    @Override
    public PayOrderDO getOrder(Long id) {
        return payOrderMapper.selectById(id);
    }

    @Override
    public PayOrderDO getOrderByNo(String no) {
        return payOrderMapper.selectByNo(no);
    }

    @Override
    public PayOrderDO getOrderByTradeNo(String tradeNo) {
        return payOrderMapper.selectByTradeNoAndStatus(tradeNo, null);
    }

    @Override
    public List<PayOrderDO> getOrderList(Collection<String> nos) {
        if (CollUtil.isEmpty(nos)) {
            return Collections.emptyList();
        }
        return payOrderMapper.selectList(new CoreLambdaQueryWrapper<PayOrderDO>()
                .in(PayOrderDO::getNo, nos));
    }

    @Override
    public PageResult<PayOrderDO> getOrderPage(PayOrderPageReqVO pageReqVO) {
        return payOrderMapper.selectPage(pageReqVO);
    }

    @Override
    public List<PayOrderDO> getOrderList(PayOrderExportReqVO exportReqVO) {
        return payOrderMapper.selectList(exportReqVO);
    }

    @Override // 注意，这里不能添加事务注解，避免调用支付渠道失败时，将 PayOrderDO 回滚了
    public PayOrderDO createOrder(PayOrderCreateDTO dto) {
        validateOrderCanCreate(dto);
        // 创建支付交易单
        PayOrderDO order = new PayOrderDO();
        order.setNo(orderSerialNoRedisRepository.generateNo(PAY_PAYMENT_NO_PREFIX));
        order.setTradeNo(dto.getTradeNo());
        order.setSubject(StrUtils.maxLength(dto.getSubject(), UnifiedOrderCreateDTO.SUBJECT_MAX_LENGTH));
        order.setBody(StrUtils.maxLength(dto.getBody(), UnifiedOrderCreateDTO.BODY_MAX_LENGTH));
        order.setPrice(dto.getPrice());
        order.setUserIp(getClientIP());
        order.setUserUa(getUserAgent());
        order.setExpireTime(LocalDateTime.now().plusMinutes(PAY_TIMEOUT_MINUTES));
        order.setStatus(UnifiedOrderStatusEnum.WAITING.getStatus());
        order.setRefundPrice(0);
        payOrderMapper.insert(order);
        return order;
    }

    private void validateOrderCanCreate(PayOrderCreateDTO dto) {
        PayOrderDO order = payOrderMapper.selectByTradeNoAndStatus(dto.getTradeNo(), UnifiedOrderStatusEnum.SUCCESS.getStatus());
        Assertions.isTrue(order == null, "订单已经支付过了");
    }

    @Override // 注意，这里不能添加事务注解，避免调用支付渠道失败时，将 PayOrderDO 回滚了
    public PayOrderSubmitRespVO submitOrder(PayOrderSubmitReqVO reqVO) {
        log.debug("[submitOrder] 后台 [reqVO({})]", reqVO);
        PayOrderDO order = validateOrderCanSubmit(reqVO.getId(), reqVO.getNo(), null);
        AppPayOrderSubmitReqVO reqVO2 = new AppPayOrderSubmitReqVO();
        reqVO2.setChannelCode(reqVO.getChannelCode());
        reqVO2.setChannelExtras(reqVO.getChannelExtras());
        reqVO2.setTradeNo(order.getTradeNo());
        return doOrderPrepay(NumberUtil.parseLong(order.getCreator()), reqVO2, order);
    }

    @Override // ！！！注意！！！这里不能添加事务注解，避免调用支付渠道失败时，将 PayOrderDO 回滚了
    public PayOrderSubmitRespVO submitOrder(Long userId, AppPayOrderSubmitReqVO reqVO) {
        log.debug("[submitOrder] 前台 [userId({}) reqVO({})]", userId, reqVO);
        PayOrderDO order = validateOrderCanSubmit(null, null, reqVO.getTradeNo());
        return doOrderPrepay(userId, reqVO, order);
    }

    private PayOrderSubmitRespVO doOrderPrepay(Long userId, AppPayOrderSubmitReqVO reqVO, PayOrderDO oldOrder) {
        PayOrderPrepayRespVO data = new PayOrderPrepayRespVO();
        PayOrderDO newPayOrder = createNewPayOrderIfNecessary(oldOrder);
        if (freePayIfNecessary(newPayOrder)) {
            return toSubmitRespVO(newPayOrder, data);
        }
        PayConfigRespDTO configRespDTO = configApiV2.getConfig(CONFIG_PAY, PayConfigRespDTO.class);
        String notifyUrl = configRespDTO.getPayNotifyUrl();
        if (StrUtil.isNotBlank(reqVO.getChannelCode())) {
            notifyUrl += "/" + reqVO.getChannelCode();
        }
        newPayOrder.setNotifyUrl(notifyUrl);
        newPayOrder.setChannelCode(reqVO.getChannelCode());
        newPayOrder.setChannelExtras(reqVO.getChannelExtras());
        UnifiedOrderCreateDTO unifiedOrderCreateDTO = buildUnifiedOrderCreateDTO(userId, reqVO, newPayOrder.getNo(), newPayOrder);
        // 获取预支付信息
        switch (UnifiedPayChannelEnum.getByCode(reqVO.getChannelCode())) {
            case MOCK: {
                Assertions.isFalse(SpringUtils.isProductionEnvironment(), "线上生产环境不允许模拟支付");
                UnifiedOrderVO orderVO = SpringUtil.getBean(MockPayClient.class).unifiedOrder(unifiedOrderCreateDTO);
                newPayOrder.setStatus(orderVO.getStatus());
                newPayOrder.setChannelOrderNo(orderVO.getChannelOrderNo());
                newPayOrder.setChannelNotifyData(JSONUtil.toJsonStr(orderVO.getRawData()));
                break;
            }
            case CASH: {
                Long extraUserId = null;
                try {
                    extraUserId = MapUtil.getLong(reqVO.getChannelExtras(), "user_id", null);
                } catch (Exception ignore) {
                }
                if (extraUserId != null && extraUserId > 0) {
                    userId = extraUserId;
                }
                Integer userType = null;
                try {
                    userType = MapUtil.getInt(reqVO.getChannelExtras(), "user_type", null);
                } catch (Exception ignore) {
                }
                // TODO 现金支付操作权限的判断后续需要优化
                Assertions.isTrue(userId != null && userId > 0 && Objects.equals(UserTypeEnum.ADMIN.getValue(), userType), "没有权限操作现金支付");
                UnifiedOrderVO orderVO = SpringUtil.getBean(CashPayClient.class).unifiedOrder(unifiedOrderCreateDTO);
                newPayOrder.setStatus(orderVO.getStatus());
                newPayOrder.setChannelOrderNo(orderVO.getChannelOrderNo());
                newPayOrder.setChannelNotifyData(JSONUtil.toJsonStr(orderVO.getRawData()));
                break;
            }
            case WALLET: {
                UnifiedOrderVO orderVO = SpringUtil.getBean(WalletPayClient.class).unifiedOrder(unifiedOrderCreateDTO);
                newPayOrder.setStatus(orderVO.getStatus());
                newPayOrder.setChannelUserId(orderVO.getChannelUserId());
                newPayOrder.setChannelOrderNo(orderVO.getChannelOrderNo());
                newPayOrder.setChannelNotifyData(JSONUtil.toJsonStr(orderVO.getRawData()));
                break;
            }
            case WX_LITE:
            case MALLBOOK_WX_LITE: {
                wxLitePrepay(reqVO, unifiedOrderCreateDTO, data, newPayOrder);
                break;
            }
            default:
                throw exception(PAY_ORDER_CHANNEL_NOT_SUPPORT);
        }
        payOrderMapper.updateById(newPayOrder);
        return toSubmitRespVO(newPayOrder, data);
    }

    private PayOrderDO createNewPayOrderIfNecessary(PayOrderDO oldOrder) {
        if (StrUtil.isBlank(oldOrder.getChannelCode())) {
            // 支付渠道为空，说明未发起支付，无需创建新的支付单
            return oldOrder;
        }
        // 每次发起支付请求都生成新的支付单号，因为 MallBook 之类的三方支付系统会校验请求单号是否重复
        PayOrderCreateDTO createDTO = new PayOrderCreateDTO();
        createDTO.setTradeNo(oldOrder.getTradeNo());
        createDTO.setSubject(oldOrder.getSubject());
        createDTO.setBody(oldOrder.getBody());
        createDTO.setPrice(oldOrder.getPrice());
        return createOrder(createDTO);
    }

    private boolean freePayIfNecessary(PayOrderDO order) {
        if (order.getPrice() > 0) {
            return false;
        }
        // 零元购、免费、免支付
        Long userId = NumberUtil.parseLong(order.getCreator());
        AppPayOrderSubmitReqVO reqVO2 = new AppPayOrderSubmitReqVO();
        reqVO2.setTradeNo(order.getTradeNo());
        reqVO2.setChannelCode(UnifiedPayChannelEnum.FREE.getCode());
        reqVO2.setChannelExtras(order.getChannelExtras());
        UnifiedOrderCreateDTO unifiedOrderCreateDTO = buildUnifiedOrderCreateDTO(userId, reqVO2, order.getNo(), order);
        UnifiedOrderVO orderVO = SpringUtil.getBean(FreePayClient.class).unifiedOrder(unifiedOrderCreateDTO);
        PayOrderDO updateObj = new PayOrderDO();
        updateObj.setId(order.getId());
        updateObj.setStatus(orderVO.getStatus());
        updateObj.setChannelCode(UnifiedPayChannelEnum.FREE.getCode());
        updateObj.setChannelOrderNo(orderVO.getChannelOrderNo());
        updateObj.setChannelNotifyData(JSONUtil.toJsonStr(orderVO.getRawData()));
        payOrderMapper.updateById(updateObj);
        log.info("[freePayIfNecessaryAsync][免支付({})]", order.getTradeNo());
        return true;
    }

    private void wxLitePrepay(AppPayOrderSubmitReqVO reqVO, UnifiedOrderCreateDTO unifiedOrderCreateDTO, PayOrderPrepayRespVO data, PayOrderDO updateObj) {
        String openid = MapUtil.getStr(reqVO.getChannelExtras(), "openid", "");
        Assertions.isTrue(StrUtil.isNotBlank(openid), "微信开放标识获取失败，无法使用微信小程序支付");
        //UnifiedOrderVO orderVO = SpringUtil.getBean(WxLitePayClient.class).unifiedOrder(unifiedOrderCreateDTO);
        UnifiedOrderVO orderVO = SpringUtil.getBean(MallBookWxLitePayClient.class).unifiedOrder(unifiedOrderCreateDTO);
        data.setDisplayMode(orderVO.getDisplayMode());
        data.setDisplayContent(orderVO.getDisplayContent());
        updateObj.setChannelUserId(openid);
        updateObj.setChannelOrderNo(orderVO.getChannelOrderNo());
        updateObj.setChannelNotifyData(JSONUtil.toJsonStr(orderVO.getRawData()));
        updateObj.setChannelErrorCode(orderVO.getChannelErrorCode());
        updateObj.setChannelErrorMsg(orderVO.getChannelErrorMsg());
        if (UnifiedPayChannelEnum.WX_LITE.getCode().equals(reqVO.getChannelCode()) && StrUtil.equals(orderVO.getChannelErrorCode(), "ORDERPAID")) {
            updateObj.setStatus(UnifiedOrderStatusEnum.SUCCESS.getStatus());
        } else if (UnifiedPayChannelEnum.MALLBOOK_WX_LITE.getCode().equals(reqVO.getChannelCode())) {
            updateObj.setStatus(orderVO.getStatus());
        }
    }

    @Nonnull
    private PayOrderSubmitRespVO toSubmitRespVO(PayOrderDO order, PayOrderPrepayRespVO data) {
        // 此处需要读取最新的状态
        order = payOrderMapper.selectById(order.getId());
        if (UnifiedOrderStatusEnum.isSuccess(order.getStatus())) {
            payOrderProducer.sendPaySuccessMessage(order.getTradeNo(), order.getNo());
        }
        PayOrderSubmitRespVO respVO = PayOrderConvert.INSTANCE.convertSubmit(order);
        respVO.setData(data);
        return respVO;
    }

    private UnifiedOrderCreateDTO buildUnifiedOrderCreateDTO(Long userId, AppPayOrderSubmitReqVO reqVO, String payNo, PayOrderDO order) {
        Integer userType = null;
        try {
            userType = MapUtil.getInt(reqVO.getChannelExtras(), "user_type", null);
        } catch (Exception ignore) {
        }
        if (userType == null) {
            userType = UserTypeEnum.MEMBER.getValue();
        }
        UnifiedOrderCreateDTO dto = new UnifiedOrderCreateDTO();
        dto.setChannelCode(reqVO.getChannelCode());
        dto.setTradeNo(reqVO.getTradeNo());
        dto.setPayNo(payNo);
        dto.setPrice(order.getPrice());
        dto.setSubject(order.getSubject());
        dto.setBody(order.getBody());
        dto.setUserIp(order.getUserIp());
        dto.setUserUa(order.getUserUa());
        dto.setExpireTime(order.getExpireTime());
        dto.setNotifyUrl(order.getNotifyUrl());
        Map<String, String> channelExtras = reqVO.getChannelExtras();
        if (channelExtras == null) {
            channelExtras = new HashMap<>();
        }
        if (UnifiedPayChannelEnum.isWxLite(reqVO.getChannelCode())) {
            channelExtras.put("app_id", wechatClientApi.getWxMaAppId(userType));
            String openid = MapUtil.getStr(channelExtras, "openid", "");
            if (StrUtil.isBlank(openid)) {
                Long extraUserId = null;
                try {
                    extraUserId = MapUtil.getLong(dto.getChannelExtras(), "user_id", null);
                } catch (Exception ignore) {
                }
                if (extraUserId != null && extraUserId > 0) {
                    userId = extraUserId;
                }
                if (userId != null && userId > 0) {
                    SocialUserRespDTO socialUserRespDTO = socialUserApi.getSocialUserByUserId(UserTypeEnum.MEMBER.getValue(), userId, UnifiedPayChannelEnum.isWxLite(reqVO.getChannelCode()) ? SocialTypeEnum.WECHAT_MINI_PROGRAM.getType() : SocialTypeEnum.WECHAT_MP.getType());
                    if (socialUserRespDTO != null) {
                        openid = socialUserRespDTO.getOpenid();
                    }
                }
            }
            if (StrUtil.isBlank(openid)) {
                // 未登录的情况下支付，如扫商家收款码付款，此时需要通过`wx.login`静默获取 code 来换取 openid
                String authCode = MapUtil.getStr(reqVO.getChannelExtras(), "auth_code", "");
                if (StrUtil.isBlank(authCode)) {
                    authCode = MapUtil.getStr(reqVO.getChannelExtras(), "code", "");
                }
                if (StrUtil.isNotBlank(authCode)) {
                    openid = wechatClientApi.getWxMaOpenId(authCode);
                }
            }
            if (StrUtil.isBlank(openid)) {
                throw exception(PAY_ORDER_CHANNEL_WX_LITE_OPENID_REQUIRED);
            }
            channelExtras.put("openid", openid);
        }
        dto.setChannelExtras(channelExtras);
        return dto;
    }

    private PayOrderDO validateOrderCanSubmit(Long id, String no, String tradeNo) {
        PayOrderDO order = null;
        if (id != null && id > 0) {
            order = payOrderMapper.selectById(id);
        } else if (StrUtil.isNotBlank(no)) {
            order = payOrderMapper.selectByNo(no);
        } else if (StrUtil.isNotBlank(tradeNo)) {
            order = payOrderMapper.selectByTradeNoAndStatus(tradeNo, null);
        }
        Assertions.notNull(order, PAY_ORDER_NOT_FOUND.getMsg());
        if (UnifiedOrderStatusEnum.isSuccess(order.getStatus())) {
            // 校验状态，发现已支付
            payOrderProducer.sendPaySuccessMessage(order.getTradeNo(), order.getNo());
            if (!Objects.equals(order.getChannelCode(), UnifiedPayChannelEnum.FREE.getCode())) {
                // 免费订单不报异常，单独判断
                throw exception(PAY_ORDER_STATUS_IS_SUCCESS);
            }
        }
        if (UnifiedOrderStatusEnum.isClosed(order.getStatus())) {
            payOrderProducer.sendPayCloseMessage(order.getTradeNo(), order.getNo());
            throw new KnownServiceException("订单已关闭，不能支付");
        }
        if (!UnifiedOrderStatusEnum.isWaiting(order.getStatus())) {
            // 校验状态，必须是待支付
            throw exception(PAY_ORDER_STATUS_IS_NOT_WAITING);
        }
        if (Objects.equals(UnifiedPayChannelEnum.WALLET.getCode(), order.getChannelCode())) {
            // 同步获取三方支付订单状态进行兜底
            UnifiedOrderVO orderVO = SpringUtil.getBean(WalletPayClient.class).getOrder(order.getNo());
            validUnifiedOrderInfo(orderVO, order);
        } else if (UnifiedPayChannelEnum.isWxLite(order.getChannelCode())) {
            // 同步获取三方支付订单状态进行兜底
            //UnifiedOrderVO orderVO = SpringUtil.getBean(WxLitePayClient.class).getOrder(order.getNo());
            UnifiedOrderVO orderVO = SpringUtil.getBean(MallBookWxLitePayClient.class).getOrder(order.getNo());
            validUnifiedOrderInfo(orderVO, order);
        }
        if (LocalDateTimeUtils.beforeNow(order.getExpireTime())) {
            // 校验是否过期
            PayOrderDO updateObj = new PayOrderDO();
            updateObj.setId(order.getId());
            updateObj.setStatus(UnifiedOrderStatusEnum.CLOSED.getStatus());
            payOrderMapper.updateById(updateObj);
            payOrderProducer.sendPayExpireMessage(order.getTradeNo(), order.getNo());
            throw exception(PAY_ORDER_IS_EXPIRED);
        }
        return order;
    }

    private void validUnifiedOrderInfo(UnifiedOrderVO orderVO, PayOrderDO order) {
        if (UnifiedOrderStatusEnum.isSuccess(orderVO.getStatus())) {
            PayOrderDO updateObj = new PayOrderDO();
            updateObj.setId(order.getId());
            updateObj.setStatus(UnifiedOrderStatusEnum.SUCCESS.getStatus());
            updateObj.setChannelUserId(orderVO.getChannelUserId());
            updateObj.setChannelOrderNo(orderVO.getChannelOrderNo());
            updateObj.setChannelNotifyData(JsonUtils.toJsonString(orderVO));
            updateObj.setChannelErrorCode(orderVO.getChannelErrorCode());
            updateObj.setChannelErrorMsg(orderVO.getChannelErrorMsg());
            payOrderMapper.updateById(updateObj);
            payOrderProducer.sendPaySuccessMessage(order.getTradeNo(), order.getNo());
            throw exception(PAY_ORDER_STATUS_IS_SUCCESS);
        }
    }

    @Override
    public void updateOrderRefundPrice(Long id, Integer incRefundPrice) {
        PayOrderDO order = payOrderMapper.selectById(id);
        if (order == null) {
            throw exception(PAY_ORDER_NOT_FOUND);
        }
        if (!UnifiedOrderStatusEnum.isSuccessOrRefund(order.getStatus())) {
            throw exception(PAY_ORDER_REFUND_FAIL_STATUS_ERROR);
        }
        if (order.getRefundPrice() + incRefundPrice > order.getPrice()) {
            throw exception(REFUND_PRICE_EXCEED);
        }

        // 更新订单
        PayOrderDO updateObj = new PayOrderDO();
        updateObj.setRefundPrice(order.getRefundPrice() + incRefundPrice);
        updateObj.setStatus(UnifiedOrderStatusEnum.REFUND.getStatus());
        int updateCount = payOrderMapper.updateByIdAndStatus(id, order.getStatus(), updateObj);
        if (updateCount == 0) {
            throw exception(PAY_ORDER_REFUND_FAIL_STATUS_ERROR);
        }
    }

    @Override
    public void updatePayOrderPrice(String no, Integer payPrice) {
        PayOrderDO order = payOrderMapper.selectByNo(no);
        if (order == null) {
            throw exception(PAY_ORDER_NOT_FOUND);
        }
        if (ObjectUtil.notEqual(UnifiedOrderStatusEnum.WAITING.getStatus(), order.getStatus())) {
            throw exception(PAY_ORDER_STATUS_IS_NOT_WAITING);
        }
        if (ObjectUtil.equal(order.getPrice(), payPrice)) {
            return;
        }
        log.warn("支付单【{}】的应付金额调整，旧金额：{}，新金额：{}", order.getNo(), order.getPrice(), payPrice);
        PayOrderDO updateObj = new PayOrderDO();
        updateObj.setId(order.getId());
        updateObj.setPrice(payPrice);
        payOrderMapper.updateById(updateObj);
    }

    @Override
    public int syncOrder(LocalDateTime minCreateTime) {
        // 1. 查询指定创建时间前的待付款单据
        List<PayOrderDO> orders = payOrderMapper.selectListByStatusAndGreaterThanCreateTime(
                UnifiedOrderStatusEnum.WAITING.getStatus(), minCreateTime);
        if (CollUtil.isEmpty(orders)) {
            return 0;
        }
        // 2. 遍历执行
        int count = 0;
        for (PayOrderDO order : orders) {
            count += syncOrder(order) ? 1 : 0;
        }
        return count;
    }

    @Override
    public PayOrderRespVO syncOrderQuietly(PayOrderDO order) {
        syncOrder(order);
        // 重新查询，因为同步后，可能会有变化
        order = payOrderMapper.selectByNo(order.getNo());
        return BeanUtils.toBean(order, PayOrderRespVO.class);
    }

    @Override
    public int syncOrderByEmptyNotify() {
        List<PayOrderDO> orders = payOrderMapper.selectList(new CoreLambdaQueryWrapper<PayOrderDO>()
                .in(PayOrderDO::getChannelCode, List.of(UnifiedPayChannelEnum.WX_LITE.getCode(), UnifiedPayChannelEnum.MALLBOOK_WX_LITE.getCode()))
                .isNull(PayOrderDO::getChannelNotifyData));
        if (CollUtil.isEmpty(orders)) {
            return 0;
        }
        log.info("[requestSyncOrder] 没有微信小程序支付通知数据的支付订单数：{}", orders.size());
        // 2. 遍历执行
        int count = 0;
        for (PayOrderDO order : orders) {
            count += syncOrder(order) ? 1 : 0;
        }
        return count;
    }

    @Override
    public boolean syncOrder(PayOrderDO order) {
        // 同步第三方渠道的支付结果
        try {
            lock.lock();
            String tradeNo = order.getTradeNo();
            String payNo = order.getNo();
            PayClient<?> payClient;
            if (UnifiedPayChannelEnum.isWxLite(order.getChannelCode())) {
                //payClient = SpringUtil.getBean(WxLitePayClient.class);
                payClient = SpringUtil.getBean(MallBookWxLitePayClient.class);
            } else if (Objects.equals(UnifiedPayChannelEnum.WALLET.getCode(), order.getChannelCode())) {
                payClient = SpringUtil.getBean(WalletPayClient.class);
            } else if (Objects.equals(UnifiedPayChannelEnum.MOCK.getCode(), order.getChannelCode())) {
                payClient = SpringUtil.getBean(MockPayClient.class);
            } else if (Objects.equals(UnifiedPayChannelEnum.CASH.getCode(), order.getChannelCode())) {
                payClient = SpringUtil.getBean(CashPayClient.class);
            } else if (Objects.equals(UnifiedPayChannelEnum.FREE.getCode(), order.getChannelCode())) {
                payClient = SpringUtil.getBean(FreePayClient.class);
            } else {
                log.warn("[requestSyncOrder] 不支持的渠道({})", order.getChannelCode());
                return false;
            }
            UnifiedOrderVO orderVO = payClient.getOrder(order.getNo());
            String rawData = orderVO.getRawData() == null ? "" : JsonUtils.toJsonString(orderVO.getRawData());
            LambdaUpdateWrapper<PayOrderDO> updateWrapper = new LambdaUpdateWrapper<>();
            updateWrapper.eq(PayOrderDO::getNo, payNo);
            updateWrapper.set(PayOrderDO::getChannelNotifyData, rawData);
            updateWrapper.set(PayOrderDO::getChannelErrorCode, orderVO.getChannelErrorCode());
            updateWrapper.set(PayOrderDO::getChannelErrorMsg, orderVO.getChannelErrorMsg());
            if (StrUtil.isNotBlank(orderVO.getChannelOrderNo())) {
                updateWrapper.set(PayOrderDO::getChannelOrderNo, orderVO.getChannelOrderNo());
            }
            if (UnifiedOrderStatusEnum.isSuccess(orderVO.getStatus())) {
                updateWrapper.set(PayOrderDO::getStatus, UnifiedOrderStatusEnum.SUCCESS.getStatus());
                LocalDateTime successTime = orderVO.getSuccessTime();
                if (successTime == null) {
                    successTime = LocalDateTime.now();
                }
                updateWrapper.set(PayOrderDO::getSuccessTime, successTime);
            } else if (UnifiedOrderStatusEnum.isClosed(orderVO.getStatus())) {
                updateWrapper.set(PayOrderDO::getStatus, UnifiedOrderStatusEnum.CLOSED.getStatus());
            }
            boolean b = payOrderMapper.update(updateWrapper) > 0;
            log.info("[requestSyncOrder][tradeNo({}) payNo({}) 的 rawData({}) status({}) 更新结果 {}]",
                    tradeNo, payNo, rawData, orderVO.getStatus(), b);
            if (b) {
                // 此处需要读取最新的状态，兜底异步回调支付成功的情况
                order = payOrderMapper.selectByNo(payNo);
                if (UnifiedOrderStatusEnum.isSuccess(order.getStatus())) {
                    payOrderProducer.sendPaySuccessMessage(order.getTradeNo(), order.getNo());
                } else if (UnifiedOrderStatusEnum.isClosed(order.getStatus())) {
                    payOrderProducer.sendPayCloseMessage(order.getTradeNo(), order.getNo());
                }
            }
            return b;
        } finally {
            lock.unlock();
        }
    }

    @Override
    public int expireOrder() {
        // 1. 查询过期的待付款单据
        List<PayOrderDO> orders = payOrderMapper.selectListByStatusAndLessThanExpireTime(
                UnifiedOrderStatusEnum.WAITING.getStatus(), LocalDateTime.now());
        if (CollUtil.isEmpty(orders)) {
            return 0;
        }
        // 2. 遍历执行
        int count = 0;
        for (PayOrderDO order : orders) {
            count += expireOrder(order) ? 1 : 0;
        }
        return count;
    }

    @Override
    public String notify(String channelCode, Map<String, String> params, String body, Map<String, String> headers) {
        log.info("[pay/notify][channelCode({}) 收到回调数据]\nparams: {}\nbody: {}\nheaders: {}", channelCode, params, body, headers);
        if (UnifiedPayChannelEnum.isWxLite(channelCode)) {
            //UnifiedOrderVO orderVO = SpringUtil.getBean(WxLitePayClient.class).parseOrderNotify(params, body, headers);
            UnifiedOrderVO orderVO = SpringUtil.getBean(MallBookWxLitePayClient.class).parseOrderNotify(params, body, headers);
            PayOrderDO order = payOrderMapper.selectByNo(orderVO.getPayNo());
            Assertions.notNull(order, "支付单不存在：payNo=" + orderVO.getPayNo());
            syncOrder(order);
            return JsonUtils.toJsonString(orderVO);
        } else {
            throw new KnownServiceException(GlobalErrorCodeConstants.NOT_IMPLEMENTED.getCode(), "渠道支付回调未实现：" + channelCode);
        }
    }

    @Override
    public void closeOrderByNo(String no) {
        if (StrUtil.isBlank(no)) {
            return;
        }
        PayOrderDO order = payOrderMapper.selectByNo(no);
        Assertions.notNull(order, "支付单不存在：payNo=" + no);
        closeOrder(order);
    }

    @Override
    public void closeOrderByTradeNo(String tradeNo) {
        if (StrUtil.isBlank(tradeNo)) {
            return;
        }
        PayOrderDO order = payOrderMapper.selectByTradeNoAndStatus(tradeNo, UnifiedOrderStatusEnum.WAITING.getStatus());
        Assertions.notNull(order, "未支付的支付单不存在：tradeNo=" + tradeNo);
        closeOrder(order);
    }

    @Override
    public List<String> getPayNoByWeChatPayed(LocalDateTime minDateTime) {
        return payOrderMapper.selectPayNoByWeChatPayed(minDateTime);
    }

    private void closeOrder(PayOrderDO order) {
        PayOrderRespVO payOrderRespVO = syncOrderQuietly(order);
        if (UnifiedOrderStatusEnum.isWaiting(payOrderRespVO.getStatus())) {
            PayOrderDO updateObj = new PayOrderDO();
            updateObj.setId(order.getId());
            updateObj.setStatus(UnifiedOrderStatusEnum.CLOSED.getStatus());
            payOrderMapper.updateById(updateObj);
            payOrderProducer.sendPayCloseMessage(order.getTradeNo(), order.getNo());
            return;
        }
        throw new KnownServiceException("支付单不是待付款状态，不能关闭");
    }

    /**
     * 同步单个支付单
     *
     * @param order 支付单
     * @return 是否已过期
     */
    public boolean expireOrder(PayOrderDO order) {
        if (UnifiedOrderStatusEnum.isClosed(order.getStatus())) {
            log.error("[expireOrder][tradeNo({}) 已关闭，可能是数据不一致]", order.getTradeNo());
            return false;
        }
        if (UnifiedOrderStatusEnum.isSuccess(order.getStatus())) {
            log.error("[expireOrder][tradeNo({}) 已支付，可能是数据不一致]", order.getTradeNo());
            return false;
        }
        if (UnifiedOrderStatusEnum.isWaiting(order.getStatus())) {
            PayOrderDO updateObj = new PayOrderDO();
            updateObj.setId(order.getId());
            updateObj.setStatus(UnifiedOrderStatusEnum.CLOSED.getStatus());
            payOrderMapper.updateById(updateObj);
            closeThirdOrder(order);
            payOrderProducer.sendPayCloseMessage(order.getTradeNo(), order.getNo());
        }
        // 同步第三方支付结果
        return syncOrder(order);
    }

    private void closeThirdOrder(PayOrderDO order) {
        if (UnifiedOrderStatusEnum.isSuccess(order.getStatus())) {
            throw new KnownServiceException("支付单已付款，不能关单");
        }
        if (UnifiedOrderStatusEnum.isSuccessOrRefund(order.getStatus())) {
            throw new KnownServiceException("支付单已退款，不能关单");
        }
        if (UnifiedPayChannelEnum.isWxLite(order.getChannelCode())) {
            //SpringUtil.getBean(WxLitePayClient.class).closeOrder(order.getNo());
            SpringUtil.getBean(MallBookWxLitePayClient.class).closeOrder(order.getNo());
        } else {
            log.warn("[closeThirdOrder][tradeNo({}) 渠道({}) 关单功能未实现或不支持]", order.getTradeNo(), order.getChannelCode());
        }
    }

}
